package com.geor.gcv.core.extract;

import java.util.ArrayList;
import java.util.List;

import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import com.geor.gcv.core.mat.Mats;
import com.geor.gcv.core.model.Area;

public class OutlineExtractor extends AbstractExtractor {

	protected int addWidth = 10;
	protected int addHeight = 1;

	public int getAddHeight() {
		return addHeight;
	}

	public int getAddWidth() {
		return addWidth;
	}

	public OutlineExtractor(int addWidth, int addHeight) {
		super();
		this.addWidth = addWidth;
		this.addHeight = addHeight;
	}

	@Override
	public void execute(Mat step) {
		try {
			areas = new ArrayList<Area>();

			Mat bin = getBin(step);
			if (bin.rows() == 0 || bin.cols() == 0) {
				return;
			}
			// addImage(bin);
			List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
			Mat hierarchy = new Mat();
			Imgproc.findContours(bin, contours, hierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_NONE);
			hierarchy.release();
			bin.release();
			for (MatOfPoint mp : contours) {
				Rect rect = Imgproc.boundingRect(mp);
				// 过滤一下区域
				Area area = new Area().toAreaByRect(rect).expandHeight(addHeight).expandWidth(addWidth);

				area = filterArea(area);
				if (area == null)
					continue;

				areas.add(area.expandAny(4));
				mp.release();
			}
		} finally {
			Mats.releseMat(step);
		}
	}

	protected Area filterArea(Area area) {
		if (area.getWidth() / area.getHeight() <= 3) {
			return null;
		} else if (area.getWidth() / area.getHeight() >= 20) {
			return null;
		} else if (area.getWidth() < area.getHeight()) {
			return null;
		} else if ((long) area.getHeight() * (long) area.getWidth() <= 600) {
			return null;
		} else if ((long) area.getHeight() * (long) area.getWidth() >= 40000) {
			return null;
		}
		return area;
	}

	private Mat getBin(Mat mat) {
		// 合并相邻像素
		Mat shard = Mats.bin(mat, 21, 30);
		Mat temp = Mat.ones(new Size(shard.cols() / addWidth, shard.rows() / addHeight), shard.type());
		int y = 0;
		for (int row = 0; row <= shard.rows()
				- (shard.rows() % addHeight == 0 ? 1 : (shard.rows() % addHeight)); row += addHeight) {
			int x = 0;
			for (int col = 0; col <= shard.cols()
					- (shard.cols() % addWidth == 0 ? 1 : (shard.cols() % addWidth)); col += addWidth) {
				int arrays[][] = new int[addHeight][addWidth];
				int leftUpX = col;
				int leftUpY = row;
				int rightDownX = col + addWidth - 1;
				int rightDownY = row + addHeight - 1;
				initArray(shard, arrays, leftUpY, leftUpX, rightDownY, rightDownX);
				int index = getIndex(arrays);

				if (index > 1) {
					temp.put(y, x++, Mats.PEX_WHITE);
				} else {
					temp.put(y, x++, Mats.PEX_BLACK);
				}
			}
			y++;
		}

		Mats.releseMat(shard);
		return temp;
	}

	private int getIndex(int[][] arrays) {
		int index = 0;
		for (int as[] : arrays) {
			for (int t : as) {
				if (t > 100) {
					index++;
				}
			}
		}
		return index;
	}

	private void initArray(Mat src, int[][] a, int leftUpHeight, int leftUpWidth, int rightDownHeight,
			int rightDownWidth) {
		int i = 0;
		for (int y = leftUpHeight; y <= rightDownHeight; y++) {
			int j = 0;
			for (int x = leftUpWidth; x <= rightDownWidth; x++) {
				if (y < 0 || x < 0 || y >= src.rows() || x >= src.cols()) {
					a[i][j++] = -1;
				} else {
					a[i][j++] = (int) src.get(y, x)[0];
				}
			}
			i++;
		}
	}

	@Override
	public List<Area> get() {
		return areas;
	}

}
